import {
  Component,
  OnInit,
  TemplateRef,
  ViewChild,
  Input,
  ViewEncapsulation,
  Output,
  EventEmitter,
  ViewContainerRef,
  ChangeDetectionStrategy,
  ElementRef,
  Renderer2,
  ChangeDetectorRef,
  ContentChild,
  forwardRef,
  AfterViewInit,
  OnDestroy,
} from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
import { AnimationEvent } from '@angular/animations';
import { Overlay, OverlayConfig } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import { isObservable, Observable, Subject } from 'rxjs';
import { takeUntil, debounceTime } from 'rxjs/operators';
import { isString as _isString, isNumber as _isNumber, find as _find } from 'lodash';
import {
  OverlayService,
  InputBoolean,
  I18NService,
  LvSelectPCB,
  LvFilterMode,
  LvSelectSM,
  LvSizeLD,
  showItem,
  InputNumber,
  SelectRemoveChangeProps,
} from '../core/api';
import { TreeComponent, TreeNode } from '../tree/api';
import { POSITION_MAP, ADJUST_POSITION_MAP, getPosition } from './treeselect-overlay-position';
import { TreeSelectTriggerComponent } from './treeselect-trigger.component';

@Component({
  selector: 'lv-tree-select',
  exportAs: 'lvTreeselect',
  templateUrl: './treeselect.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef((): typeof TreeSelectComponent => TreeSelectComponent),
      multi: true,
    },
  ],
  animations: [showItem],
  host: {
    class: 'lv-tree-select',
    '[class.lv-treeselect-open]': 'selectOpen',
    '[class.disabled]': 'lvDisabled',
    '[class.lv-tree-select-size-large]': "lvSize === 'large'",
    '[class.lv-tree-select-size-default]': "lvSize === 'default'",
  },
})
export class TreeSelectComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnDestroy {
  // tree
  @Input() lvTreeData; // 源数据
  @Input() lvPanelClass: string;
  @Input() lvTreeBeforeSelected; // 选中之前的回调
  @Input() lvFilterKey = 'label';
  @Input() lvSize: LvSizeLD = 'default';
  @Input() @InputBoolean() lvLoading = false;
  @Input() @InputNumber() lvVirtualMaxBuffer; // 渲染新项时最大缓冲高度
  @Input() @InputNumber() lvVirtualMinBuffer; // 视图之外渲染的最小缓冲区数，若缓存区数量低于此数量将渲染新项
  @Input() @InputBoolean() lvDisabled = false; // 是否禁用
  @Input() @InputBoolean() lvShowClear = false; // 是否显示清除按钮
  @Input() @InputBoolean() lvTreeAsync: boolean; // 是否为异步
  // @Input() lvTreeSelection; // 选中项
  @Input() lvTreeNodeTemplate: TemplateRef<any>; // tree模板

  @Input() @InputNumber() lvMaxTagCount: number; // 选中项个数模式下，最多显示多少个tag
  @Input() @InputBoolean() lvFilterFocus = true; // 过滤输入框自动聚焦
  @Input() @InputBoolean() lvTreeShowLine = true; // 是否展示连接线

  // search
  @Input() @InputBoolean() lvShowFilter: boolean; // 是否显示搜索框
  @Input() @InputNumber() lvMaxTagRow: number = 7; // 选中项单行模式下，点了+n之后，超过7（包含收起，可配置）行则出现滚动条

  @Output() lvScrollEnd = new EventEmitter<any>(); // 虚拟滚动到底部时回调事件
  @Input() lvFilterMode: LvFilterMode = 'contains';
  @Input() @InputBoolean() lvVirtualScroll = false; // 虚拟滚动
  @Output() lvOpenChange = new EventEmitter<any>(); // 切换选择下拉回调事件

  @Output() lvClearChange = new EventEmitter<any>(); // 清除全部时回调事件
  @Input() lvSelectedValueMode: LvSelectPCB = 'both';
  @Output() lvFilterChange = new EventEmitter<any>(); // 搜索过滤回调事件
  @Output() lvRemoveChange = new EventEmitter<any>(); // 删除单个时回调事件

  @Input() lvTreeSelectionMode: LvSelectSM = 'single'; // 选择模式

  @Input() @InputBoolean() lvTreeShowCheckbox: boolean; // 是否显示checkbox

  @Input() @InputBoolean() lvBlockNode: boolean = true;
  @Output() lvExpandedChange = new EventEmitter<any>(); // 节点展开变化回调
  @Input() lvPanelWidth: string | number = 'adaptInput';

  @Output() lvScrollingChange = new EventEmitter<any>(); // 虚拟滚动中回调事件

  @Input() lvTreeCompareWith: string | ((o: any) => any); // data被选中的比较方式，默认地址比较
  @Input() @InputBoolean() lvTreeShowContentIcon: boolean; // 是否显示内容前icon
  @Input() @InputBoolean() lvKeepExpanded: boolean = true; // 过滤条件清除之后是否保留展开收起的能力

  @Input() @InputBoolean() lvTreeSelectionAssociate = true; // 选择关联，选中是否关联父子选中
  @Input() @InputBoolean() lvTreeSelectedByCheckbox = false; // 是否只能checkbox选中
  @Output() lvTreeSelectionChange = new EventEmitter<any>(); // 选中项变化回调
  @Input() lvScroll: { y?: string | null } = {
    y: '288px',
  };

  @ViewChild(TreeComponent, { static: false }) lvTree: TreeComponent;
  // header
  @Input() lvPlaceholder: string = this.i18n.get('selectPlaceholder'); // 如果没有选择任何值，显示的占位符
  // 挂载源
  @ViewChild('treeselectTemplate', { static: true }) treeselectTemplate: TemplateRef<any>;
  @Input() lvTreeShowExpandedIcon: boolean | { expandIcon: string; collapseIcon: string } = true; // 展开折叠图标

  @ContentChild(TreeSelectTriggerComponent, { static: false }) customTrigger: TreeSelectTriggerComponent;
  @Input() lvBeforeOpen: () =>
    | (false | void | object)
    | Promise<false | void | object>
    | Observable<boolean | void | object>;

  @Input() lvPosition: 'topLeft' | 'topCenter' | 'topRight' | 'bottomLeft' | 'bottomCenter' | 'bottomRight' =
    'bottomLeft';

  value;
  // 组件DOM
  public element;
  noData = false;
  treeModelValue: TreeNode[];
  selectOpen: boolean = false; // 下拉展开状态
  triggerVirtualScroll = false;
  public animationState = 'void';
  public overlayShow$ = new Subject<boolean>();

  private _opened;
  // 动画
  private _direction;
  // overlay
  private _overlayRef;
  private _activeElement: any;
  private destroy$ = new Subject<void>();
  private _refresh$ = new Subject<void>();
  private openState$ = new Subject<boolean>();
  constructor(
    public viewContainerRef: ViewContainerRef,
    public overlayService: OverlayService,
    public overlay: Overlay,
    public elementRef: ElementRef,
    public renderer: Renderer2,
    public cdr: ChangeDetectorRef,
    public i18n: I18NService,
  ) {
    this._activeElement = elementRef.nativeElement;
  }

  get multiple(): boolean {
    return this.lvTreeSelectionMode === 'multiple';
  }

  // 是否显示单选项清除按钮
  get showSelectedClear(): boolean {
    return this.lvShowClear && this.treeModelValue?.length && !this.multiple && !this.lvDisabled;
  }

  registerOnTouched(): void {}

  /**
   * ngFor遍历的 trackBy函数，防止数据更新导致所有DOM重新渲染
   */
  trackByIndex(index): any {
    return index;
  }

  setRefresh(): void {
    this._refresh$.next();
  }

  scrollEnd(): void {
    this.lvScrollEnd.emit();
  }

  scrollingChange(e): void {
    this.lvScrollingChange.emit(e);
  }

  stopPropagation(e: MouseEvent): void {
    e.stopPropagation();
  }

  registerOnChange(fn: any): void {
    this._onModelChange = fn;
  }

  // 节点展开变化回调
  expandedChange(event): void {
    this.lvExpandedChange.emit(event);
  }

  writeValue(value: any): void {
    this.treeModelValue = value;
    this.cdr.markForCheck();
  }

  ngOnDestroy(): void {
    this.clearOverlayRef();
    this.destroy$.next();
    this.destroy$.complete();
  }

  close(): void {
    if (this._overlayRef?.overlayRef.hasAttached()) {
      this._overlayRef.close();
    }
  }

  clearOverlayRef(): void {
    if (this._overlayRef?.overlayRef.hasAttached()) {
      this._overlayRef.overlayRef.dispose();
    }
  }

  // 单选且已选中，关闭overlay
  nodeCheck(event): void {
    if (this.lvTreeSelectionMode === 'single' && this.lvTree.isSelected(event.node)) {
      this.close();
    }
  }

  ngAfterViewInit(): void {
    // 设置组件触发DOM，和指令配合使用
    this.element = this.element ? this.element : this.elementRef.nativeElement;
    this.initializationTrigger();
  }

  // 更新overlay位置
  updateOverlayPosition(): void {
    setTimeout((): void => {
      if (this._overlayRef?.overlayRef.hasAttached()) {
        this._overlayRef.overlayRef.updatePosition();
      }
    });
  }

  ngOnInit(): void {
    this._refresh$.pipe(debounceTime(1), takeUntil(this.destroy$)).subscribe((): void => {
      this.triggerVirtualScroll = this.treeModelValue?.length > 50;
      this.cdr.markForCheck();
    });
  }

  /**
   * 注册触发器，判断执行显示隐藏条件
   */
  initializationTrigger(): void {
    this.renderer.listen(this.element, 'click', (): void => {
      if (this._overlayRef?.overlayRef.hasAttached()) {
        this.close();
      } else {
        this.open();
      }
    });
  }

  onAnimationDone(event: AnimationEvent): void {
    const isUp = event.fromState === 'showUp' && event.toState === 'hideUp';
    const isDown = event.fromState === 'showDown' && event.toState === 'hideDown';
    if (isUp || isDown) {
      this.openState$.next(false);
    }
  }

  beforeClose(): void {
    this.openState$.next(true);
    if (this._direction === 'up') {
      this.animationState = 'hideUp';
      this.cdr.markForCheck();
    } else {
      this.animationState = 'hideDown';
      this.cdr.markForCheck();
    }
  }

  // tree操作 选中项变化回调(单选且已选中不取消选中，故不触发)
  selectionChange(event): void {
    this.treeModelValue = [...this.treeModelValue];
    this.lvTreeSelectionChange.emit(event);
    this._onModelChange(event);
    this.setRefresh();
    this.updateOverlayPosition();

    if (this.lvTreeSelectionMode === 'single') {
      this.close();
    }
  }

  // 单选时清除
  clearSelected(e: MouseEvent): void {
    e.stopPropagation();
    this.treeModelValue = this.treeModelValue.filter((treeNode): any | boolean => treeNode.disabled);
    this.lvTreeSelectionChange.emit(this.treeModelValue);
    this._onModelChange(this.treeModelValue);
    this.lvClearChange.emit();
    this.clearOverlayRef();
  }

  // 获取panel宽度
  getPanelWidth(inputWidth): any {
    if (this.lvPanelWidth === 'adaptInput') {
      return inputWidth;
    } else if (this.lvPanelWidth === 'auto') {
      return 'auto';
    } else if (_isString(this.lvPanelWidth)) {
      return this.lvPanelWidth;
    } else if (_isNumber(this.lvPanelWidth)) {
      return `${this.lvPanelWidth}px`;
    } else {
      return inputWidth;
    }
  }

  treeSearch(event): void {
    this.lvTree?._scrollViewport && this.lvTree._scrollViewport?.scrollTo({ top: 0 });
    this.lvTree.filter({ filterMode: this.lvFilterMode, key: this.lvFilterKey, value: event });
    this.lvTree._dataSource.connect().subscribe((): void => {
      this.noData = !_find(this.lvTree.lvData, ['hidden', false]);
    });

    this.lvFilterChange.emit(event);
  }

  beforeOpen(resolve): void {
    const openFn = (_resolve): void => {
      _resolve();
    };
    if (!this.lvBeforeOpen) {
      openFn(resolve);
    } else {
      const cb = this.lvBeforeOpen();
      if (cb instanceof Promise) {
        cb.then((res): void => {
          if (res !== false) {
            openFn(resolve);
          }
        });
      } else if (isObservable(cb)) {
        cb.subscribe((res): void => {
          if (res !== false) {
            openFn(resolve);
          }
        });
      } else if (cb !== false) {
        openFn(resolve);
      }
    }
  }

  afterOpen(overlayInstance): void {
    // 设置z-index
    this.renderer.addClass(this._overlayRef.overlayRef.hostElement, 'lv-treeselect-z-index');
    this.selectOpen = true;
    // 获取打开的方向
    overlayInstance.overlayRef._positionStrategy.positionChanges.subscribe((e): void => {
      if (this._opened) {
        return;
      }
      const _position = getPosition(e.connectionPair);
      if (_position === 'topLeft' || _position === 'topCenter' || _position === 'topRight') {
        this.animationState = 'showUp';
        this._direction = 'up';
      } else {
        this.animationState = 'showDown';
        this._direction = 'down';
      }
      this._opened = true;
      this.cdr.markForCheck();
    });
  }

  // 多选清除
  removeSelectedItem(props: SelectRemoveChangeProps): void {
    const { event, node } = props;
    event.stopPropagation();

    const nodeValue = new Set(this.treeModelValue);
    const updateTreeModelValue = (treeNode: TreeNode): void => {
      nodeValue.delete(treeNode);

      // node有parent时，需要从选项中删除父节点
      if (treeNode.parent) {
        nodeValue.delete(treeNode.parent);
      }

      // node有children时，需要从选项中删除所有子节点
      if (treeNode.children) {
        for (const child of treeNode.children) {
          updateTreeModelValue(child);
        }
      }
    };

    if (!this.lvTreeSelectionAssociate) {
      nodeValue.delete(node);
    } else {
      updateTreeModelValue(node);
    }

    this.treeModelValue = Array.from(nodeValue);

    this.lvTreeSelectionChange.emit(this.treeModelValue);
    this._onModelChange(this.treeModelValue);
    this.lvRemoveChange.emit(node);

    this.setRefresh();
    this.updateOverlayPosition();
  }

  open(): void {
    if (this._overlayRef?.overlayRef.hasAttached() || this.lvDisabled) {
      return;
    }
    const selectWidth = this._activeElement.clientWidth;
    const panelWidth = this.getPanelWidth(selectWidth);
    const config = new OverlayConfig({
      // minWidth: selectWidth,
      width: panelWidth,
      hasBackdrop: false,
      positionStrategy: this.overlay
        .position()
        .flexibleConnectedTo(this.element)
        .withPositions([POSITION_MAP[this.lvPosition], ...ADJUST_POSITION_MAP])
        .withGrowAfterOpen(true), // 使overlay初始打开后可以灵活的增加宽度/高度
      scrollStrategy: this.overlay.scrollStrategies.block(),
    });

    const extendConfig = {
      beforeOpen: (): Promise<any> =>
        new Promise((resolve): void => {
          this.beforeOpen(resolve);
        }),
      afterOpen: (overlayRef): void => {
        this._overlayRef = overlayRef;
        this.afterOpen(overlayRef);
        this.lvOpenChange.emit(this.selectOpen);
        if (this.value) {
          setTimeout((): void => {
            this.treeSearch(this.value);
          });
        }
      },
      beforeClose: (): Promise<void> =>
        new Promise<void>((resolve): void => {
          this.beforeClose();

          const closesubscribe = this.openState$.subscribe((open): void => {
            if (!open) {
              closesubscribe.unsubscribe();
              resolve();
            }
          });
        }),
      afterClose: (): void => {
        this.selectOpen = false;
        this._opened = false;
        this.lvOpenChange.emit(this.selectOpen);
        this.setRefresh();
      },
    };

    const portal = new TemplatePortal(this.treeselectTemplate, this.viewContainerRef);
    this.overlayService.open(portal, config, extendConfig);
  }

  private _onModelChange: any = (): void => {};
}
